using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Threading;

namespace Dida.Dbg
{
	public interface IDebugDrawTarget
	{
		void DrawLine(Vector3D start, Vector3D end, UInt32 colour);
		void DrawBox(Box box, UInt32 colour);
		void DrawSphere(Sphere sphere, UInt32 colour);
		void DrawLocator(Vector3D pos, float size, UInt32 colour);
		void DrawDebugMesh(DebugMesh mesh);
		void DrawDebugMesh(DebugMesh mesh, Vector3D pos, EulerRot rot, Vector3D scale);
		void PushWorldMatrix(Matrix4 mat);
		void PopWorldMatrix();
		void ApplyWorldMatrix(Matrix4 mat);

		IGeometryDebugDraw GeometryDebugDraw
		{
			get;
		}
	}

	public static class DebugDraw
	{
		static Stack<Matrix4> worldMatrices;
		static DebugDrawImpl impl;

		static Dictionary<string, DebugDrawContext> contexts = new Dictionary<string,DebugDrawContext>();
		static Dictionary<string, DebugDrawContext> toAdd = new Dictionary<string,DebugDrawContext>();
		static Dictionary<string, DebugDrawContext> toRemove = new Dictionary<string,DebugDrawContext>();
		static ReaderWriterLock contextsLock = new ReaderWriterLock();
		static ReaderWriterLock toAddLock = new ReaderWriterLock();
		static ReaderWriterLock toRemoveLock = new ReaderWriterLock();

		static bool isDrawing = false;
		
		static DebugDraw()
		{
			worldMatrices = new Stack<Matrix4>();
			worldMatrices.Push(Matrix4.Identity);
		}

		/// <summary>
		/// Gets or sets the debug draw implementation.
		/// </summary>
		/// <remarks>
		/// This property should not be set when you're in a 
		/// <see cref = "BeginDrawing"/> / <see cref = "EndDrawing"/> block.
		/// </remarks>
		/// <seealso cref="DebugDrawImpl"/>
		public static DebugDrawImpl Implementation
		{
			set
			{
				Debug.Assert(!isDrawing, "You can't change the implementation " + 
					"while you're inside a BeginDrawing/EndDrawing block");
				impl = value; 
			}
			get { return impl; }
		}

		/// <summary>
		/// Pushes a copy of the current matrix onto the matrix stack.
		/// You can restore the matrix to the current state, by popping
		/// the new matrix from the stack, using the 
		/// <see cref = "PopWorldMatrix">PopWorldMatrix</see> method.
		/// </summary>
		public static void PushWorldMatrix()
		{
			worldMatrices.Push(worldMatrices.Peek());
			if (impl != null)
				impl.SetWorldMatrix(worldMatrices.Peek());
		}

		/// <summary>
		/// <para>
		/// Applies the given matrix to the matrix on top of the stack,
		/// and pushes it onto the stack. You can restore the matrix stack
		/// to the state it had before calling this method with the
		/// <see cref = "PopWorldMatrix">PopWorldMatrix</see> method.
		/// </para>
		/// <para>
		/// In the context of this method, applying a matrix means that the 
		/// transformation of the given matrix would be applied to the vertices
		/// you transform, after the transformations that are already 
		/// in the current matrix. ie. The current matrix will be multiplied 
		/// with the matrix you are applying.
		/// </para>
		/// </summary>
		/// <param name="mat"></param>
		public static void PushWorldMatrix(Matrix4 mat)
		{
			worldMatrices.Push(worldMatrices.Peek() * mat);
			if (impl != null)
				impl.SetWorldMatrix(worldMatrices.Peek());
		}

		/// <summary>
		/// Pops the top of the matrix stack. The current matrix
		/// will be restored to the matrix of before you called the
		/// <see cref = "PushWorldMatrix(Matrix4)">PushWorldMatrix</see> method.
		/// </summary>
		public static void PopWorldMatrix()
		{
			worldMatrices.Pop();
			if (impl != null)
				impl.SetWorldMatrix(worldMatrices.Peek());
		}

		/// <summary>
		/// <para>
		/// Applies the given matrix to the top of the matrix stack. The old
		/// top isn't pushed, so you can't restore it afterwards.
		/// </para>
		/// <para>
		/// In the context of this method, applying a matrix means that the 
		/// transformation of the given matrix would be applied to the vertices
		/// you transform, after the transformations that are already 
		/// in the current matrix. ie. The current matrix will be multiplied 
		/// with the matrix you are applying.
		/// </para>
		/// </summary>
		/// <param name="mat">The matrix that should be applied on top
		/// of the current transformations</param>
		/// <seealso cref="PushWorldMatrix(Matrix4)"/>
		public static void ApplyWorldMatrix(Matrix4 mat)
		{
			worldMatrices.Push(worldMatrices.Pop() * mat);
			if (impl != null)
				impl.SetWorldMatrix(worldMatrices.Peek());
		}

		/// <summary>
		/// <para>
		/// This method updates and displays the current 
		/// <see cref = "DebugDrawContext">DebugDrawContexts</see>. This
		/// method should be called each graphics frame, from inside a
		/// <see cref = "BeginDrawing">BeginDrawing</see> / 
		/// <see cref = "EndDrawing">EndDrawing</see> block.
		/// </para>
		/// <para>
		/// In the update step, things like timing out for timed contexts
		/// is calculated. Also, the actual removing and adding of
		/// new/outdated contexts to the main context list happens at 
		/// this step.
		/// </para>
		/// <para>
		/// In the drawing step, the last frame of each context,
		/// that was completed will be rendered to the screen.
		/// </para>
		/// </summary>
		/// <param name="timeDelta">The time, in seconds, that has
		/// been passed since the previous time this method was called.</param>
		public static void UpdateAndDrawContexts(float timeDelta)
		{
			Debug.Assert(isDrawing, "The UpdateAndDrawContexts must be called from inside a BeginDrawing/EndDrawing block");

			ApplyContextsModifications();			

			contextsLock.AcquireReaderLock(-1);
			foreach (KeyValuePair<string,DebugDrawContext> i in contexts)
			{
				PushWorldMatrix();
				i.Value.Update(timeDelta);
				i.Value.Draw();
				PopWorldMatrix();

				if (i.Value.IsTimed)
				{
					if (i.Value.IsTimedOut)
					{
						toRemoveLock.AcquireWriterLock(-1);

						if (!toRemove.ContainsKey(i.Key))
							toRemove.Add(i.Key, i.Value);

						toRemoveLock.ReleaseLock();
					}
				}
			}
			contextsLock.ReleaseLock();
		}

		/// <summary>
		/// Applies the pending modifications in the toAdd and toRemove lists,
		/// to the contexts list.
		/// </summary>
		private static void ApplyContextsModifications()
		{
			// apply toAdd and toRemove
			contextsLock.AcquireWriterLock(-1);

			toRemoveLock.AcquireWriterLock(-1);
			foreach (KeyValuePair<string, DebugDrawContext> i in toRemove)
				contexts.Remove(i.Key);
			toRemove.Clear();
			toRemoveLock.ReleaseLock();

			toAddLock.AcquireWriterLock(-1);
			foreach (KeyValuePair<string, DebugDrawContext> i in toAdd)
				contexts.Add(i.Key, i.Value);
			toAdd.Clear();
			toAddLock.ReleaseLock();

			contextsLock.ReleaseLock();
		}

		/// <summary>
		/// <para>
		/// Gets a <see cref="DebugDrawContext">DebugDrawContext</see> 
		/// with the given name. If a <see cref="DebugDrawContext">DebugDrawContext</see> 
		/// with the given name doesn't exist yet, a new one will be created.
		/// </para>
		/// <para>
		/// A <see cref="DebugDrawContext">DebugDrawContext</see> context allows
		/// you to render debug stuff to the screen, asynchroneous from the 
		/// graphics rendering framerate.
		/// A <see cref="DebugDrawContext">DebugDrawContext</see> will always
		/// show the latest completed frame, and won't disappear until it is removed 
		/// using the <see cref = "RemoveDebugDrawContext">
		/// RemoveDebugDrawContext</see> method.
		/// </para>
		/// <para>
		/// It is also possible to create a <see cref="DebugDrawContext">DebugDrawContext</see> 
		/// that will disappear after a given timeout. You can 
		/// create such a context using the 
		/// <see cref = "GetTimedDebugDrawContext">GetTimedDebugDrawContext</see>
		/// method.</para>
		/// <para>
		/// This method is thread safe, ie. You can call it from any thread, at
		/// any time.
		/// </para>
		/// </summary>
		/// <param name="name">The name of the <see cref="DebugDrawContext">DebugDrawContext</see> </param>
		/// <returns>The <see cref="DebugDrawContext">DebugDrawContext</see></returns>
		public static DebugDrawContext GetDebugDrawContext(string name)
		{
			contextsLock.AcquireReaderLock(-1);

			DebugDrawContext context;
			if (contexts.TryGetValue(name, out context))
			{
				toRemoveLock.AcquireReaderLock(-1);
				if (toRemove.ContainsKey(name))
				{
					// it was removed, but not processed yet, and now it
					// is requested again, so we just remove it from the
					// toRemove list

					toRemoveLock.UpgradeToWriterLock(-1);
					toRemove.Remove(name);
				}
				toRemoveLock.ReleaseLock();

				Debug.Assert(!context.IsTimed,"The GetDebugDrawContext " +
					"method should not be called on debug contexts that " +
					"were earlier requested as timed contexts");
			}
			else
			{
				// maybe it's still in the toAdd list
				toAddLock.AcquireReaderLock(-1);
				if (toAdd.ContainsKey(name))
				{
					// it already exists, so we return the one from the toAdd list
					context = toAdd[name];
				}
				else
				{
					// we have to create a new context
					context = new DebugDrawContext();

					toAddLock.UpgradeToWriterLock(-1);
					toAdd.Add(name, context);
				}
				toAddLock.ReleaseLock();
			}

			contextsLock.ReleaseReaderLock();
			return context;
		}

		public static DebugDrawContext GetTimedDebugDrawContext(string name, float timeOut)
		{
			contextsLock.AcquireReaderLock(-1);

			DebugDrawContext context;
			if (contexts.TryGetValue(name, out context))
			{
				toRemoveLock.AcquireReaderLock(-1);
				if (toRemove.ContainsKey(name))
				{
					// it was removed, but not processed yet, and now it
					// is requested again, so we just remove it from the
					// toRemove list

					toRemoveLock.UpgradeToWriterLock(-1);
					toRemove.Remove(name);
				}
				toRemoveLock.ReleaseLock();

				Debug.Assert(!context.IsTimed, "The GetDebugDrawContext " +
					"method should not be called on debug contexts that " +
					"were earlier called on timed contexts");
			}
			else
			{
				// maybe it's still in the toAdd list
				toAddLock.AcquireReaderLock(-1);
				if (toAdd.ContainsKey(name))
				{
					// it already exists, so we return the one from the toAdd list
					context = toAdd[name];
				}
				else
				{
					// we have to create a new context
					context = new DebugDrawContext(timeOut);

					toAddLock.UpgradeToWriterLock(-1);
					toAdd.Add(name, context);
				}
				toAddLock.ReleaseLock();
			}

			context.ResetTimeOut(timeOut);

			contextsLock.ReleaseReaderLock();
			return context;
		}

		/// <summary>
		/// <para>
		/// Removes the DebugDrawContext with the given name from the scene,
		/// so that it won't be visible any more.
		/// </para>
		/// <para>
		/// This method is thread safe, ie. It can be called from
		/// any thread at any time.
		/// </para>
		/// </summary>
		/// <param name="name">The name of the DebugDrawContext.</param>
		public static void RemoveDebugDrawContext(string name)
		{
			toRemoveLock.AcquireWriterLock(-1);
			if (!toRemove.ContainsKey(name))
				toRemove.Add(name,null);
			toRemoveLock.ReleaseLock();
		}

		/// <summary>
		/// <para>
		/// Removes all DebugDrawContexts from the scene.
		/// </para>
		/// <para>
		/// This method is thread safe, ie. It can be called from
		/// any thread at any time.
		/// </para>
		/// </summary>
		/// <seealso cref="RemoveDebugDrawContext"/>
		public static void RemoveAllDebugDrawContexts()
		{
			contextsLock.AcquireReaderLock(-1);
			toRemoveLock.AcquireWriterLock(-1);

			foreach (string key in contexts.Keys)
			{
				if(!toRemove.ContainsKey(key))
					toRemove.Add(key,null);
			}

			toRemoveLock.ReleaseLock();
			contextsLock.ReleaseLock();
		}

		public static void BeginDrawing(Viewpoint view)
		{
			Debug.Assert(impl != null, "No debug draw implementation set");
			Debug.Assert(!isDrawing, "You can't nest BeginDrawing calls");

			worldMatrices.Clear();
			worldMatrices.Push(Matrix4.Identity);
			
			impl.Begin(view);
			impl.SetWorldMatrix(worldMatrices.Peek());

			isDrawing = true;
		}

		public static void EndDrawing()
		{
			Debug.Assert(impl != null, "No debug draw implementation set");
			Debug.Assert(isDrawing, "Error in EndDrawing: You haven't called BeginDrawing");
			
			impl.End();

			isDrawing = false;
		}

		/// <summary>
		/// <para>
		/// Draws a line.
		/// </para>
		/// This method must be called from inside a
		/// <see cref = "BeginDrawing">BeginDrawing</see> / 
		/// <see cref = "EndDrawing">EndDrawing</see> block.
		/// </summary>
		/// <param name="start">The start vertex of the line.</param>
		/// <param name="end">The end vertex of the line.</param>
		/// <param name="colour">The colour of the line.</param>
		public static void DrawLine(Vector3D start, Vector3D end, UInt32 colour)
		{
			Debug.Assert(isDrawing, "DrawLine must be called from inside a BeginDraw / EndDraw block");
			impl.DrawLine(start, end, colour);
		}

		public static void DrawTriangle(Vector3D[] verts, UInt32 colour)
		{
			Debug.Assert(isDrawing, "DrawTriangle must be called from inside a BeginDraw / EndDraw block");
			impl.DrawTriangle(verts, colour);
		}

		public static void DrawTriangle(Vector3D v1, Vector3D v2, Vector3D v3, UInt32 colour)
		{
			Debug.Assert(isDrawing, "DrawTriangle must be called from inside a BeginDraw / EndDraw block");
			impl.DrawTriangle(new Vector3D[] { v1, v2, v3 }, colour);
		}

		public static void DrawQuad(Vector3D[] verts, uint colour)
		{
			impl.DrawTriangle(new Vector3D[] { verts[0], verts[1], verts[3] }, colour);
			impl.DrawTriangle(new Vector3D[] { verts[3], verts[1], verts[2] }, colour);
		}

		public static void DrawQuad(Vector3D v1, Vector3D v2, Vector3D v3, Vector3D v4, uint colour)
		{
			impl.DrawTriangle(new Vector3D[] { v1, v2, v4 }, colour);
			impl.DrawTriangle(new Vector3D[] { v4, v2, v3 }, colour);
		}

		/// <summary>
		/// <para>
		/// Draws a wireframe box.
		/// </para>
		/// This method must be called from inside a
		/// <see cref = "BeginDrawing">BeginDrawing</see> /
		/// <see cref = "EndDrawing">EndDrawing</see> block.
		/// </summary>
		/// <param name="box">The box you want to draw.</param>
		/// <param name="colour">The colour of the box.</param>
		public static void DrawBox(Box box, UInt32 colour)
		{
			Debug.Assert(isDrawing, "DrawBox must be called from inside a BeginDraw / EndDraw block");

			DrawLine(box.GetCorner(0), box.GetCorner(1), colour);
			DrawLine(box.GetCorner(1), box.GetCorner(3), colour);
			DrawLine(box.GetCorner(3), box.GetCorner(2), colour);
			DrawLine(box.GetCorner(2), box.GetCorner(0), colour);

			DrawLine(box.GetCorner(4), box.GetCorner(5), colour);
			DrawLine(box.GetCorner(5), box.GetCorner(7), colour);
			DrawLine(box.GetCorner(7), box.GetCorner(6), colour);
			DrawLine(box.GetCorner(6), box.GetCorner(4), colour);

			DrawLine(box.GetCorner(0), box.GetCorner(4), colour);
			DrawLine(box.GetCorner(1), box.GetCorner(5), colour);
			DrawLine(box.GetCorner(3), box.GetCorner(7), colour);
			DrawLine(box.GetCorner(2), box.GetCorner(6), colour);
		}

		/// <summary>
		/// <para>
		/// Draws a wireframe sphere.
		/// </para>
		/// This method must be called from inside a
		/// <see cref = "BeginDrawing">BeginDrawing</see> /
		/// <see cref = "EndDrawing">EndDrawing</see> block.
		/// </summary>
		/// <param name="sphere">The sphere you want to draw.</param>
		/// <param name="colour">The colour of the sphere.</param>
		public static void DrawSphere(Sphere sphere, UInt32 colour)
		{
			Debug.Assert(isDrawing, "DrawSphere must be called from inside a BeginDraw / EndDraw block");

			const int nOnLatitude = 3;
			const int nOnLongitude = 8;
			const int nSegments = 24;

			// first draw the meridians
			for (int i = 0; i < nOnLongitude; i++)
			{
				float circleAngle = (float)i / nOnLongitude * 2 * (float)Math.PI;
				float circleX = (float)Math.Sin(circleAngle);
				float circleZ = (float)Math.Cos(circleAngle);

				float endY = 0;
				float endRad = sphere.radius;
				for (int j = 0; j < nSegments; j++)
				{
					float startY = endY;
					float startRad = endRad;

					float endAngle = (float)(j + 1) / nSegments * 2 * (float)Math.PI;
					endY = (float)Math.Sin(endAngle) * sphere.radius;
					endRad = (float)Math.Cos(endAngle) * sphere.radius;

					DrawLine(new Vector3D(circleX * startRad, startY, circleZ * startRad) + sphere.center,
						new Vector3D(circleX * endRad, endY, circleZ * endRad) + sphere.center, colour);
				}
			}

			// draw those things that are equal to the equator (can't
			// remember what they are called atm.)
			for (int i = 0; i < nOnLatitude; i++)
			{
				float latitudeAngle = (float)(i + 1) / (nOnLatitude + 2) * (float)Math.PI;
				float rad = (float)Math.Sin(latitudeAngle) * sphere.radius;
				float circleY = (float)Math.Cos(latitudeAngle) * sphere.radius;

				float endX = 0;
				float endZ = rad;
				for (int j = 0; j < nSegments; j++)
				{
					float startX = endX;
					float startZ = endZ;

					float endAngle = (float)(j + 1) / nSegments * 2 * (float)Math.PI;
					endX = (float)Math.Sin(endAngle) * rad;
					endZ = (float)Math.Cos(endAngle) * rad;

					DrawLine(new Vector3D(startX, circleY, startZ) + sphere.center,
						new Vector3D(endX, circleY, endZ) + sphere.center, colour);
				}
			}
		}

		/// <summary>
		/// <para>
		/// Draws a locator. A locator looks like a cross, and can be used to 
		/// identify a point.
		/// </para>
		/// This method must be called from inside a
		/// <see cref = "BeginDrawing">BeginDrawing</see> /
		/// <see cref = "EndDrawing">EndDrawing</see> block.
		/// </summary>
		/// <param name="pos">The position of the locator</param>
		/// <param name="size">The size of the locator. (Measured from one side
		/// to the other, not from the center.)</param>
		/// <param name="colour">The colour of the locator.</param>
		public static void DrawLocator(Vector3D pos, float size, UInt32 colour)
		{
			Debug.Assert(isDrawing, "DrawLocator must be called from inside a BeginDraw / EndDraw block");

			float halfSize = size * .5f;
			DrawLine(pos - new Vector3D(halfSize, 0, 0), pos + new Vector3D(halfSize, 0, 0), colour);
			DrawLine(pos - new Vector3D(0, halfSize, 0), pos + new Vector3D(0, halfSize, 0), colour);
			DrawLine(pos - new Vector3D(0, 0, halfSize), pos + new Vector3D(0, 0, halfSize), colour);
		}

		public static DebugMesh CreateDebugMesh()
		{
			return new DebugMesh(impl.CreateDebugMesh());
		}

		public static void DrawDebugMesh(DebugMesh mesh)
		{
			Debug.Assert(isDrawing, "DrawDebugMesh must be called from inside a BeginDraw / EndDraw block");

			impl.DrawDebugMesh(mesh);
		}

		public static void DrawDebugMesh(DebugMesh mesh,Vector3D pos,
			EulerRot rot,Vector3D scale)
		{
			Matrix4 mat = Matrix4.GetScaleMatrix(scale) *
				Matrix4.GetRollMatrix(rot.roll) *
				Matrix4.GetPitchMatrix(rot.pitch) *
				Matrix4.GetYawMatrix(rot.yaw) *
				Matrix4.GetTranslationMatrix(pos);
			PushWorldMatrix(mat);

			if(impl != null)
				impl.DrawDebugMesh(mesh);

			PopWorldMatrix();
		}

		private class DirectDebugDrawTarget : IDebugDrawTarget, IGeometryDebugDraw
		{
			#region IDebugDrawTarget Members

			public void DrawLine(Vector3D start, Vector3D end, uint colour)
			{
				DebugDraw.DrawLine(start, end, colour);
			}

			public void DrawBox(Box box, uint colour)
			{
				DebugDraw.DrawBox(box, colour);
			}

			public void DrawSphere(Sphere sphere, uint colour)
			{
				DebugDraw.DrawSphere(sphere, colour);
			}

			public void DrawLocator(Vector3D pos, float size, uint colour)
			{
				DebugDraw.DrawLocator(pos, size, colour);
			}

			public void DrawDebugMesh(DebugMesh mesh)
			{
				DebugDraw.DrawDebugMesh(mesh);
			}

			public void DrawDebugMesh(DebugMesh mesh, Vector3D pos, EulerRot rot, Vector3D scale)
			{
				DebugDraw.DrawDebugMesh(mesh, pos, rot, scale);
			}

			public void PushWorldMatrix(Matrix4 mat)
			{
				DebugDraw.PushWorldMatrix(mat);
			}

			public void PopWorldMatrix()
			{
				DebugDraw.PopWorldMatrix();
			}

			public void ApplyWorldMatrix(Matrix4 mat)
			{
				DebugDraw.ApplyWorldMatrix(mat);
			}

			public IGeometryDebugDraw GeometryDebugDraw
			{
				get { return this; }
			}

			#endregion
		}

		public static IDebugDrawTarget DebugDrawTarget
		{
			get { return new DirectDebugDrawTarget(); }
		}
	}

	public class DebugDrawContext : IGeometryDebugDraw, IDebugDrawTarget
	{
		private bool isTimed;
		private float timeOut;

		private delegate void DrawCmd();
		// the draw commands that are waiting to be drawn to the screen
		private List<DrawCmd> pendingDrawCmds;
		// the draw commands that are currently being created.
		private List<DrawCmd> drawingCmds;

		internal DebugDrawContext(float timeOut)
		{
			this.timeOut = timeOut;
			isTimed = true;
		}

		internal DebugDrawContext()
		{
			isTimed = false;
		}

		internal void ResetTimeOut(float timeOut)
		{
			this.timeOut = timeOut;
		}

		/// <summary>
		/// Gets a boolean value that indicates whether this is a timed context. 
		/// A timed context is a context that will disappear after
		/// a given timeout.
		/// </summary>
		public bool IsTimed
		{
			get { return isTimed; }
		}

		/// <summary>
		/// Gets a boolean value that indicates whether
		/// this timed context has timed out, and should disappear. You should
		/// only use this property on timed context.
		/// </summary>
		public bool IsTimedOut
		{
			get
			{
				Debug.Assert(isTimed);
				return timeOut < 0;
			}
		}

		/// <summary>
		/// Begins the drawing of a new frame. All drawing commands
		/// must be called in between a BeginDrawing / 
		/// <see cref = "EndDrawing">EndDrawing</see> block.
		/// </summary>
		/// <seealso cref="EndDrawing"/>
		public void BeginDrawing()
		{
			drawingCmds = new List<DrawCmd>();
		}

		/// <summary>
		/// Ends the drawing of the current frame, and makes this
		/// frame visible in the next graphics frame.
		/// </summary>
		/// <seealso cref="BeginDrawing"/>
		public void EndDrawing()
		{
			lock (this)
			{
				pendingDrawCmds = drawingCmds;
				drawingCmds = null;
			}
		}

		internal void Update(float timeDelta)
		{
			if (isTimed)
			{
				timeOut -= timeDelta;
			}
		}

		internal void Draw()
		{
			List<DrawCmd> curCmds;
			lock (this)
			{
				curCmds = pendingDrawCmds;
			}

			if (curCmds != null)
			{
				foreach (DrawCmd drawCmd in curCmds)
					drawCmd();
			}
		}

		/// <summary>
		/// <para>
		/// Draws a line.
		/// </para>
		/// This method must be called from inside a
		/// <see cref = "BeginDrawing">BeginDrawing</see> / 
		/// <see cref = "EndDrawing">EndDrawing</see> block.
		/// </summary>
		/// <param name="start">The start vertex of the line.</param>
		/// <param name="end">The end vertex of the line.</param>
		/// <param name="colour">The colour of the line.</param>
		public void DrawLine(Vector3D start, Vector3D end, UInt32 colour)
		{
			Debug.Assert(drawingCmds != null, "DrawLine must be called from inside a BeginDraw / EndDraw block");

			DrawCmd cmd = delegate()
			{
				DebugDraw.DrawLine(start, end, colour);
			};
			drawingCmds.Add(cmd);
		}

		/// <summary>
		/// <para>
		/// Draws a wireframe box.
		/// </para>
		/// This method must be called from inside a
		/// <see cref = "BeginDrawing">BeginDrawing</see> /
		/// <see cref = "EndDrawing">EndDrawing</see> block.
		/// </summary>
		/// <param name="box">The box you want to draw.</param>
		/// <param name="colour">The colour of the line.</param>
		public void DrawBox(Box box, UInt32 colour)
		{
			Debug.Assert(drawingCmds != null, "DrawBox must be called from inside a BeginDraw / EndDraw block");

			DrawCmd cmd = delegate()
			{
				DebugDraw.DrawBox(box, colour);
			};
			drawingCmds.Add(cmd);
		}

		/// <summary>
		/// <para>
		/// Draws a wireframe sphere.
		/// </para>
		/// <para>
		/// This method must be called from inside a
		/// <see cref = "BeginDrawing">BeginDrawing</see> /
		/// <see cref = "EndDrawing">EndDrawing</see> block.
		/// </para>
		/// </summary>
		/// <param name="sphere">The sphere you want to draw.</param>
		/// <param name="colour">The colour of the sphere.</param>
		public void DrawSphere(Sphere sphere, UInt32 colour)
		{
			Debug.Assert(drawingCmds != null, "DrawSphere must be called from inside a BeginDraw / EndDraw block");

			DrawCmd cmd = delegate()
			{
				DebugDraw.DrawSphere(sphere, colour);
			};
			drawingCmds.Add(cmd);
		}

		/// <summary>
		/// <para>
		/// Draws a locator. A locator looks like a cross, and can be used to 
		/// identify a point in space.
		/// </para>
		/// <para>
		/// This method must be called from inside a
		/// <see cref = "BeginDrawing">BeginDrawing</see> /
		/// <see cref = "EndDrawing">EndDrawing</see> block.
		/// </para>
		/// </summary>
		/// <param name="pos">The position of the locator</param>
		/// <param name="size">The size of the locator. (Measured from one side
		/// to the other, not from the center.)</param>
		/// <param name="colour">The colour of the locator.</param>
		public void DrawLocator(Vector3D pos, float size, UInt32 colour)
		{
			Debug.Assert(drawingCmds != null, "DrawLocator must be called from inside a BeginDraw / EndDraw block");

			DrawCmd cmd = delegate()
			{
				DebugDraw.DrawLocator(pos, size, colour);
			};
			drawingCmds.Add(cmd);
		}

		public void DrawDebugMesh(DebugMesh mesh)
		{
			Debug.Assert(drawingCmds != null, "DrawDebugMesh must be called from inside a BeginDraw / EndDraw block");

			DrawCmd cmd = delegate()
			{
				DebugDraw.DrawDebugMesh(mesh);
			};
			drawingCmds.Add(cmd);
		}

		/// <summary>
		/// Draws the given debug mesh at the given location.
		/// </summary>
		/// <param name="mesh">The <see cref = "DebugMesh"/> you want to draw.</param>
		/// <param name="pos">The position of the mesh.</param>
		/// <param name="rot">The rotation of the mesh.</param>
		/// <param name="scale">The scale of the mesh.</param>
		/// <remarks>
		/// This method must be called from inside a
		/// <see cref = "BeginDrawing"/> / <see cref = "EndDrawing"/> block.
		/// </remarks>
		public void DrawDebugMesh(DebugMesh mesh, Vector3D pos, EulerRot rot, Vector3D scale)
		{
			Debug.Assert(drawingCmds != null, "DrawDebugMesh must be called from inside a BeginDrawing / EndDrawing block");

			DrawCmd cmd = delegate()
			{
				DebugDraw.DrawDebugMesh(mesh,pos,rot,scale);
			};
			drawingCmds.Add(cmd);
		}

		/// <summary>
		/// <para>
		/// Applies the given matrix to the matrix on top of the stack,
		/// and pushes it onto the stack. You can restore the matrix stack
		/// to the state it had before calling this method using the
		/// <see cref = "PopWorldMatrix">PopWorldMatrix</see> method.
		/// </para>
		/// <para>
		/// In the context of this method, applying a matrix means that the 
		/// transformation of the given matrix would be applied to the vertices
		/// you transform, after the transformations that are already 
		/// in the current matrix. ie. The current matrix will be multiplied 
		/// with the matrix you are applying.
		/// </para>
		/// </summary>
		/// <param name="mat"></param>
		public void PushWorldMatrix(Matrix4 mat)
		{
			Debug.Assert(drawingCmds != null, "PushWorldMatrix must be called from inside a BeginDraw / EndDraw block");

			DrawCmd cmd = delegate()
			{
				DebugDraw.PushWorldMatrix(mat);
			};
			drawingCmds.Add(cmd);
		}

		public void PopWorldMatrix()
		{
			Debug.Assert(drawingCmds != null, "PopWorldMatrix must be called from inside a BeginDraw / EndDraw block");

			DrawCmd cmd = delegate()
			{
				DebugDraw.PopWorldMatrix();
			};
			drawingCmds.Add(cmd);
		}

		public void ApplyWorldMatrix(Matrix4 mat)
		{
			Debug.Assert(drawingCmds != null, "ApplyWorldMatrix must be called from inside a BeginDraw / EndDraw block");

			DrawCmd cmd = delegate()
			{
				DebugDraw.ApplyWorldMatrix(mat);
			};
			drawingCmds.Add(cmd);
		}

		IGeometryDebugDraw IDebugDrawTarget.GeometryDebugDraw
		{
			get { return (IGeometryDebugDraw)this; }
		}
	}

	public abstract class DebugDrawImpl
	{
		public abstract void Begin(Viewpoint view);
		public abstract void End();

		/// <summary>
		/// Sets the absolute world transformation matrix.
		/// </summary>
		/// <param name="mat">The new world matrix.</param>
		/// <remarks>
		/// This is the transformation that should be used for
		/// the rendering of lines, triangles and DebugMeshes
		/// that follow, until a new world matrix is set.
		/// </remarks>
		public abstract void SetWorldMatrix(Matrix4 mat);
		public abstract void DrawLine(Vector3D start, Vector3D end, UInt32 colour);
		public abstract void DrawTriangle(Vector3D[] verts,UInt32 colour);

		public abstract IDebugMeshImpl CreateDebugMesh();
		public abstract void DrawDebugMesh(DebugMesh mesh);
	}
}